package uk.ac.strath.cis.metric_spaces.distance;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import uk.ac.strath.cis.metric_spaces.framework.FingerPrintProcessor;
import uk.ac.strath.cis.metric_spaces.framework.MultivariateDistance;
import uk.ac.strath.cis.metric_spaces.framework.ProcessableDistance;

import com.google.common.collect.Multiset;

/**
 * @author Robert
 * 
 * @param <E>
 */
public class StructuralEntropicDistance<E> implements
		ProcessableDistance<E, FingerPrintProcessor<E, Multiset<Integer>>>, MultivariateDistance<E> {

	private FingerPrintProcessor<E, Multiset<Integer>> processor;

	/*
	 * (non-Javadoc)
	 * 
	 * @see uk.ac.strath.cis.rmoss.metric_spaces.framework.ProcessableDistance#
	 * setProcessor(uk.ac.strath.cis.rmoss.metric_spaces.framework.Processor)
	 */
	public void setProcessor(
			FingerPrintProcessor<E, Multiset<Integer>> processor) {
		this.processor = processor;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * uk.ac.strath.cis.rmoss.metric_spaces.framework.Distance#distance(java
	 * .lang.Object, java.lang.Object)
	 */

	public double distance(E e1, E e2) {
		List<Multiset<Integer>> fps = new ArrayList<Multiset<Integer>>();
		processor.process(e1);
		Multiset<Integer> fp1 = processor.getFingerPrint();
		fps.add(fp1);
		processor.process(e2);
		Multiset<Integer> fp2 = processor.getFingerPrint();
		fps.add(fp2);
		// return distance(fp1, fp2);
		return _distance(fps);
	}

	public double distance(Collection<E> es) {
		List<Multiset<Integer>> fps = new ArrayList<Multiset<Integer>>(
				es.size());
		for (E e : es) {
			processor.process(e);
			fps.add(processor.getFingerPrint());
		}
		return _distance(fps);
	}

	private double sumProbs(List<Multiset<Integer>> fps, int t) {
		double sumP = 0.0;
		for (Multiset<Integer> s : fps)
			sumP += relativeFrequency(t, s);
		return sumP;
	}

	public double _distance(List<Multiset<Integer>> fps) {
		int n = fps.size();
		double res = 1.0;
		for (int t : fps.get(0).elementSet()) {
			// is t in the intersection?
			boolean inAll = true;
			for (Multiset<Integer> fp : fps) {
				inAll &= fp.contains(t);
			}
			if (inAll) {

				for (Multiset<Integer> fp : fps) {
					double p_st = relativeFrequency(t, fp);
					double sum = sumProbs(fps, t);
					double u_st = p_st / sum;
					double w_t = sum / n;
					res *= Math.pow(u_st, u_st * w_t);
				}
			}
		}
		return ((n * res - 1) / (n - 1));
	}

	private double relativeFrequency(int x, Multiset<Integer> set) {
		return set.count(x) / (double) set.size();
	}
}
